package com.home.security.browser.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.home.common.constants.redis.RedisDateBase;
import com.home.common.constants.shiro.ShiroConstant;
import com.home.common.redis.config.RedisConfig;
import com.home.common.utils.string.StringUtils;
import com.home.security.browser.filter.LogoutFilter;
import com.home.security.browser.shiro.AdminWebSessionManager;
import com.home.security.browser.shiro.MyRetryLimitCredentialsMatcher;
import com.home.security.browser.shiro.ShiroRealm;
import com.home.security.browser.shiro.ShiroSessionIdGenerator;
import com.home.security.core.encryption.HashUtils;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Description: shiro 配置
 * Created by 王大宸 on 2020-11-14 1:36
 * Created with IntelliJ IDEA.
 */
@Configuration
public class ShiroConfig {
    private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
    @Autowired
    private RedisConfig redisConfig;
    @Autowired
    private ClientConfig clientConfig;

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启Shiro-aop注解支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * Shiro基础配置
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 身份认证失败，则跳转到登录页面的配置
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后自动跳转到指定的页面
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 权限认证失败，则跳转到指定页面
        shiroFilterFactoryBean.setUnauthorizedUrl(ShiroConstant.UN_AUTH_URL);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 注意过滤器配置顺序不能颠倒
        // 配置过滤:不会被拦截的链接
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("**/static/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/static/upload/**", "anon");
        filterChainDefinitionMap.put("/account/token", "anon");
        filterChainDefinitionMap.put("/wx/login", "anon");
        filterChainDefinitionMap.put("/wx/token", "anon");
        filterChainDefinitionMap.put("/forget", "anon");
        filterChainDefinitionMap.put("/open/**", "anon");
        /* 排除oss文件上传接口 */
        filterChainDefinitionMap.put("/upload/**", "anon");
        filterChainDefinitionMap.put("/error/**", "anon");
        filterChainDefinitionMap.put("/heartbeat", "anon");
        // 拦截所有
        filterChainDefinitionMap.put("/**", "authc");
        // 退出 logout地址，shiro去清除session
        // filterChainDefinitionMap.put("/auth/logout", "logout");

        // 自定义 Filter
        //  Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
        //  filters.put("logout", logoutFilter());
        //  shiroFilterFactoryBean.setFilters(filters);

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 退出过滤器
     */
    public LogoutFilter logoutFilter() {
        logger.info("正在配置退出过滤器...");
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setCacheManager(cacheManager());
        logoutFilter.setLoginUrl("/login");
        logoutFilter.setClientId(clientConfig.getClient());
        return logoutFilter;
    }


    /**
     * 安全管理器
     */
    @Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("myRetryLimitCredentialsMatcher") MyRetryLimitCredentialsMatcher matcher) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义Ssession管理
        securityManager.setSessionManager(sessionManager());
        // 自定义Cache实现
        securityManager.setCacheManager(cacheManager());
        // 自定义Realm验证
        securityManager.setRealm(shiroRealm(matcher));
        // 这个是为了自定义错误状态码
        ThreadContext.bind(securityManager);
        return securityManager;
    }

    /**
     * 配置Session管理器 实现 session 共享
     */
    @Bean
    public SessionManager sessionManager() {
        AdminWebSessionManager sessionManager = new AdminWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

    /**
     * 身份验证器
     * 将参数HashedCredentialsMatcher修改成重写后的类
     */
    @Bean
    public ShiroRealm shiroRealm(MyRetryLimitCredentialsMatcher matcher) {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(matcher);
        return shiroRealm;
    }

    /**
     * 凭证匹配器
     * 将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置
     */
    @Bean(name = "myRetryLimitCredentialsMatcher")
    public MyRetryLimitCredentialsMatcher hashedCredentialsMatcher() {
        MyRetryLimitCredentialsMatcher shaCredentialsMatcher = new MyRetryLimitCredentialsMatcher();
        // 散列算法:这里使用SHA256算法;
        shaCredentialsMatcher.setHashAlgorithmName(HashUtils.hashAlgorithmName);
        // 散列的次数，比如散列两次，相当于 md5(md5(""));
        shaCredentialsMatcher.setHashIterations(HashUtils.hashIterations);
        return shaCredentialsMatcher;
    }

    /**
     * 配置Redis管理器
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        // 老版本需要重新设置 port ,新版本在 host 里面设置IP:端口
        redisManager.setHost(redisConfig.getHostName() + ":" + redisConfig.getPort());
        redisManager.setTimeout(redisConfig.getTimeout());
        if (StringUtils.isNotEmpty(redisConfig.getPassword())) {
            redisManager.setPassword(redisConfig.getPassword());
        }
        // 用户登录信息
        redisManager.setDatabase(RedisDateBase.DEFAULT_DATE_BASE);
        return redisManager;
    }

    /**
     * 配置Cache管理器
     * 用于往Redis存储权限和角色标识,使用的是shiro-redis开源插件
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix(ShiroConstant.CACHE_KEY);
        // 配置缓存的话要求放在session里面的实体类必须有个id标识
        redisCacheManager.setPrincipalIdFieldName("id");
        return redisCacheManager;
    }

    /**
     * 配置ShiroDialect，用于Shiro和thymeleaf标签配合使用
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    /**
     * sessionId 生成
     */
    @Bean
    public ShiroSessionIdGenerator sessionIdGenerator() {
        return new ShiroSessionIdGenerator();
    }

    /**
     * 配置RedisSessionDAO
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        redisSessionDAO.setKeyPrefix(ShiroConstant.SESSION_KEY);
        redisSessionDAO.setExpire(ShiroConstant.EXPIRE);
        return redisSessionDAO;
    }


}
